package com.iwdnb.gkgz.application.strategy;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import cn.hutool.core.date.DateUtil;
import com.iwdnb.gkgz.application.model.request.AddStrategyTradeStockDataRequest;
import com.iwdnb.gkgz.common.enums.RangeTypeEnums;
import com.iwdnb.gkgz.common.model.dto.StockDayData;
import com.iwdnb.gkgz.common.model.dto.StrategyTradeDTO;
import com.iwdnb.gkgz.common.quota.MovingAverage;
import com.iwdnb.gkgz.common.utils.BigDecimalUtils;
import com.iwdnb.gkgz.common.utils.DateUtils;
import com.iwdnb.gkgz.common.utils.StockTrendUtils;
import com.iwdnb.gkgz.common.utils.StockUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;

import static com.iwdnb.gkgz.common.utils.StockUtils.getAveragePrice;

@Service
@Slf4j
public class TrendTwentyLineBackStockBackValidateService extends CommmonStockBackValidateService {

    @Override
    public List<StrategyTradeDTO> queryBackValidateDateList(String code, String strategy,
        List<StockDayData> stockDayDataList,
        AddStrategyTradeStockDataRequest request) {
        int twentyPeriod = 20;
        int thirtyPeriod = 30;
        int sixtyPeriod = 60;
        MovingAverage.calculateEMA(stockDayDataList, 20);
        MovingAverage.calculateEMA(stockDayDataList, 30);
        MovingAverage.calculateEMA(stockDayDataList, 60);
        List<String> dateList = new ArrayList<>();
        List<StrategyTradeDTO> strategyTradeDTOList = new ArrayList<>();
        for (int i = 0; i < stockDayDataList.size(); i++) {
            if (i < 60) {
                continue;
            }
            StockDayData data = stockDayDataList.get(i);
            String date = data.getDate();
            BigDecimal closePrice = data.getClosePrice();
            BigDecimal twentyAvg = getAveragePrice(data, twentyPeriod);
            //收盘价高于20日均线，跳过
            if (BigDecimalUtils.isGe(closePrice, twentyAvg)) {
                //log.debug("{}-{}收盘价高于20日均线，跳过", code, date);
                continue;
            }
            ////最低价要比20日均价小3个点以内,跳过
            //BigDecimal minRange = twentyAvg.subtract(data.getMinPrice());
            //BigDecimal minRate = BigDecimalUtils.divideToPrecent(minRange, twentyAvg);
            //if (BigDecimalUtils.isLt(minRate, new BigDecimal(3))) {
            //    //log.debug("{}-{}收盘价高于20日均线，跳过", code, date);
            //    continue;
            //}
            //前一天收盘价在20日均线下方，跳过
            StockDayData yesterdayData = stockDayDataList.get(i - 1);
            BigDecimal yesterdayClosePrice = yesterdayData.getClosePrice();
            BigDecimal yesterdayTwentyAvg = getAveragePrice(yesterdayData, twentyPeriod);
            if (BigDecimalUtils.isLe(yesterdayClosePrice, yesterdayTwentyAvg)) {
                //log.debug("{}-{}前一天收盘价在20日均线下方，跳过", code, date);
                continue;
            }
            //买入信号日前10日内出现过跌破20日线的2次以上，跳过
            List<StockDayData> beforeTenDayDatas = StockUtils.getStockDayDataListBeforeDate(stockDayDataList, date,
                10);
            int repeatSignalCount = 0;
            for (StockDayData subData : beforeTenDayDatas) {
                BigDecimal subMinPrice = subData.getMinPrice();
                BigDecimal subTwentyAvg = getAveragePrice(subData, twentyPeriod);
                if (BigDecimalUtils.isLe(subMinPrice, subTwentyAvg)) {
                    repeatSignalCount++;
                    break;
                }
            }
            if (repeatSignalCount > 1) {
                continue;
            }
            //5日前的20日均价比当天低4%以上，10日前的20日均价比当天低10%以上
            List<StockDayData> beforeFiveDayDatas = StockUtils.getStockDayDataListBeforeDate(stockDayDataList, date,
                5);
            StockDayData beforeFiveData = beforeFiveDayDatas.get(0);
            BigDecimal beforeFiveAvg = getAveragePrice(beforeFiveData, 20);
            BigDecimal beforeFiveRange = twentyAvg.subtract(beforeFiveAvg);
            BigDecimal beforeFiveRate = BigDecimalUtils.divideToPrecent(beforeFiveRange, beforeFiveAvg);
            //20日前的20日均价比当天低10%以内，跳过
            if (BigDecimalUtils.isLt(beforeFiveRate, new BigDecimal(3))) {
                //log.debug("{}-{}20日前的20日均价比当天低10%以内，跳过", code, date);
                continue;
            }
            StockDayData beforeTenData = beforeTenDayDatas.get(0);
            BigDecimal beforeTenAvg = getAveragePrice(beforeTenData, 20);
            BigDecimal beforeTenRange = twentyAvg.subtract(beforeTenAvg);
            BigDecimal beforeTenRate = BigDecimalUtils.divideToPrecent(beforeTenRange, beforeTenAvg);
            //10日前的20日均价比当天低3%以内，跳过
            if (BigDecimalUtils.isLt(beforeTenRate, new BigDecimal(10))) {
                //log.debug("{}-{}10日前的20日均价比当天低3%以内，跳过", code, date);
                continue;
            }
            String twentyTrend = StockTrendUtils.getTrend(data, stockDayDataList, twentyPeriod);
            String thirtyTrend = StockTrendUtils.getTrend(data, stockDayDataList, thirtyPeriod);
            String sixtyTrend = StockTrendUtils.getTrend(data, stockDayDataList, sixtyPeriod);
            BigDecimal thirtyAvg = getAveragePrice(data, thirtyPeriod);
            BigDecimal sixtyAvg = getAveragePrice(data, sixtyPeriod);
            //a.20、30、60日线均向上，且价格依次排布
            Boolean trendFlag = RangeTypeEnums.UP.equalsCode(twentyTrend) && RangeTypeEnums.UP.equalsCode(thirtyTrend)
                && RangeTypeEnums.UP.equalsCode(sixtyTrend)
                && BigDecimalUtils.isGe(twentyAvg, thirtyAvg) && BigDecimalUtils.isGe(thirtyAvg, sixtyAvg);
            if (!trendFlag) {
                //log.debug("{}-{}趋势向下，跳过", code, date);
                continue;
            }
            //3日内有反弹回20日线上方，且不超过5个点
            List<StockDayData> afterThreeDayDatas = StockUtils.getStockDayDataListAfterDate(stockDayDataList, date, 3);
            boolean backFlag = false;
            StockDayData backDayData = null;
            BigDecimal subOpenPrice = data.getClosePrice();
            for (StockDayData subData : afterThreeDayDatas) {
                BigDecimal subClosePrice = subData.getClosePrice();
                BigDecimal subTwentyAvg = getAveragePrice(subData, twentyPeriod);
                BigDecimal subLowPrice = BigDecimalUtils.isGt(subClosePrice, subData.getOpenPrice())
                    ? subData.getOpenPrice() : subClosePrice;
                //BigDecimal subLowRange = subLowPrice.subtract(subData.getMinPrice());
                //BigDecimal subLowRate = BigDecimalUtils.divideToPrecent(subLowRange, subData.getMinPrice());
                //if(BigDecimalUtils.isGt(subLowRate,new BigDecimal(3))){
                //    backFlag = true;
                //    backDayData = subData;
                //    break;
                //}else
                if (BigDecimalUtils.isGt(subClosePrice, subTwentyAvg)) {
                    //如果收盘价不超过20均线5个点
                    BigDecimal subRange = subClosePrice.subtract(subTwentyAvg);
                    BigDecimal subRate = BigDecimalUtils.divideToPrecent(subRange, subTwentyAvg);
                    //log.debug("{}-{}反弹超过20日均线", code, subDate);
                    if (BigDecimalUtils.isLt(subRate, new BigDecimal(3))) {
                        //log.debug("{}-{}反弹未超过3个点,反弹成立", code, subDate);
                        backFlag = true;
                        backDayData = subData;
                        break;
                    }
                }
            }
            //如果没有反弹,直接跳过
            if (!backFlag) {
                //log.debug("{}-{}没有反弹，跳过", code, date);
                continue;
            }
            //反弹日上影线大于3个点,跳过
            BigDecimal backDayUpRange = backDayData.getMaxPrice().subtract(backDayData.getClosePrice());
            BigDecimal backDayUpRate = BigDecimalUtils.divideToPrecent(backDayUpRange, backDayData.getClosePrice());
            if (BigDecimalUtils.isGt(backDayUpRate, new BigDecimal(3))) {
                continue;
            }
            //反弹日之前13天的最低价,应该是在买入日之后
            List<StockDayData> beforeFifteenDatas = StockUtils.getStockDayDataListBeforeDate(stockDayDataList,
                backDayData.getDate(), 15);
            BigDecimal minPrice = beforeFifteenDatas.get(0).getMinPrice();
            String minDate = beforeFifteenDatas.get(0).getDate();
            for (StockDayData subData : beforeFifteenDatas) {
                BigDecimal subMinPrice = subData.getMinPrice();
                if (BigDecimalUtils.isLt(subMinPrice, minPrice)) {
                    minPrice = subMinPrice;
                    minDate = subData.getDate();
                }
            }
            //最小价格日出现在买入信号之前,跳过
            if (DateUtil.parseDate(date).getTime() > DateUtil.parseDate(minDate).getTime()) {
                continue;
            }
            ////成交量近似
            //List<StockDayData> beforeFourDayDatas = StockUtils.getStockDayDataListBeforeDate(stockDayDataList,
            // date, 4);
            //beforeFourDayDatas.add(data);
            //int tradeNumSum = beforeFourDayDatas.stream().collect(Collectors.summingInt(o -> o.getTradeNum()));
            //double tradeNum = (tradeNumSum / 5.0) * 0.8;
            //tradeNum = tradeNum > data.getTradeNum() ? data.getTradeNum() : tradeNum;
            ////反弹日成交量不到平均成交量的8折,跳过
            //if (tradeNum > backDayData.getTradeNum()) {
            //    //log.debug("{}-{}反弹日成交量不到平均成交量的8折，跳过", code, date);
            //    continue;
            //}
            String buyDate = backDayData.getDate();
            if (!dateList.contains(buyDate)) {
                dateList.add(buyDate);
                StrategyTradeDTO strategyTradeDTO = new StrategyTradeDTO();
                strategyTradeDTO.setCode(code);
                strategyTradeDTO.setUuid("trendTwentyBack");
                strategyTradeDTO.setBuyDate(DateUtil.parseDate(buyDate));
                strategyTradeDTO.setBuyPrice(backDayData.getClosePrice());
                strategyTradeDTO.setStatus("buy");
                strategyTradeDTOList.add(strategyTradeDTO);
                //BigDecimal profit = caculateAfterDaysProfit(backDayData.getDate(), stockDayDataList, 5);
                //log.debug("{}-{}买入,未来5天最高收益为:{}", code, buyDate, profit);
            }

        }
        return strategyTradeDTOList;
    }

    @Override
    public void doBackValidate(String code, String strategy,
        List<StockDayData> stockDayDataList, List<StrategyTradeDTO> strategyTradeDTOList) {
        List<String> dateList = strategyTradeDTOList.stream().map(o -> DateUtil.formatDate(o.getBuyDate()))
            .collect(Collectors.toList());
        Date latestEndDate = strategyTradeDTOList.get(0).getBuyDate();
        latestEndDate = DateUtil.offsetDay(latestEndDate, -1);
        for (StrategyTradeDTO strategyTradeDTO : strategyTradeDTOList) {
            Date d = strategyTradeDTO.getBuyDate();
            if (DateUtils.beforeOrEquals(d, latestEndDate)) {
                log.debug("{}-{}在上一个周期内,忽略", code, d);
                continue;
            }
            getTwentyBackTradeInfo(code, stockDayDataList, strategyTradeDTO);
            if (Objects.nonNull(strategyTradeDTO)) {
                latestEndDate = strategyTradeDTO.getSellDate();
                strategyTradeDTOList.add(strategyTradeDTO);
            }
        }
    }

    private void getTwentyBackTradeInfo(String code, List<StockDayData> stockDayDataList,
        StrategyTradeDTO strategyTradeDTO) {
        String date = DateUtil.formatDate(strategyTradeDTO.getBuyDate());
        StockDayData buyDayData = StockUtils.getStockDayData(stockDayDataList, date);
        BigDecimal buyPrice = buyDayData.getClosePrice();
        List<StockDayData> subList = StockUtils.getStockDayDataListAfterDate(stockDayDataList, date, 5);
        if (CollectionUtils.isEmpty(subList) || subList.size() < 5) {
            return;
        }
        BigDecimal rate = null;
        String sellDate = null;
        String info = null;
        BigDecimal sellPrice = null;
        int holdIndex = 0;
        for (int i = 0; i < 5; i++) {
            StockDayData data = subList.get(i);
            BigDecimal closePrice = data.getClosePrice();
            BigDecimal openPrice = data.getOpenPrice();
            BigDecimal range = BigDecimalUtils.subtract(closePrice, buyPrice);
            rate = BigDecimalUtils.divideToPrecent(range, buyPrice);
            sellDate = data.getDate();
            sellPrice = data.getClosePrice();
            holdIndex = i;
            //上影线大于3个点,卖出
            BigDecimal upPrice = BigDecimalUtils.isGt(openPrice, closePrice) ? openPrice : closePrice;
            BigDecimal upRange = data.getMaxPrice().subtract(upPrice);
            BigDecimal upRate = BigDecimalUtils.divideToPrecent(upRange, upPrice);
            if (BigDecimalUtils.isGt(upRate, new BigDecimal(3)) && i < 2) {
                log.debug("{}-{}-上影线大于3个点，卖出，收益={}", code, data.getDate(), rate);
                info = "上影线大于3个点，卖出";
                break;
            }
            //亏损大于5个点，卖出
            if (BigDecimalUtils.isLe(rate, new BigDecimal(-5))) {
                log.debug("{}-{}-亏损大于5个点，卖出，收益={}", code, data.getDate(), rate);
                info = "亏损大于5个点，卖出";
                break;
            }
            //第一天亏损大于3个点，卖出
            if (i == 0 && BigDecimalUtils.isLe(rate, new BigDecimal(-3))) {
                log.debug("{}-{}-第一天亏损大于3个点，卖出，收益={}", code, data.getDate(), rate);
                info = "第一天亏损大于3个点，卖出";
                break;
            }
            BigDecimal dayRange = BigDecimalUtils.subtract(closePrice, openPrice);
            BigDecimal dayRate = BigDecimalUtils.divideToPrecent(dayRange, openPrice);
            //单日亏损大于5个点，卖出
            if (BigDecimalUtils.isLe(dayRate, new BigDecimal(-5))) {
                log.debug("{}-{}-单日亏损大于5个点，卖出，收益={}", code, data.getDate(), rate);
                info = "亏损大于5个点，卖出";
                break;
            }
            //3天内的收益小于3个点，卖出
            if (i == 2 && BigDecimalUtils.isLt(rate, new BigDecimal(3))) {
                log.debug("{}-{}-3天内的收益小于3个点，卖出，收益={}", code, data.getDate(), rate);
                info = "3天内的收益小于3个点，卖出";
                break;
            }
            if (i == 1) {
                BigDecimal firstDayPrice = subList.get(0).getClosePrice();
                if (BigDecimalUtils.isLt(firstDayPrice, buyPrice)
                    && BigDecimalUtils.isLt(data.getClosePrice(), firstDayPrice)) {
                    log.debug("{}-{}-连续2天下跌，卖出，收益={}", code, data.getDate(), rate);
                    info = "连续2天下跌，卖出";
                    break;
                }
            }
            //3天内的收益大于15个点，卖出
            if (BigDecimalUtils.isGt(rate, new BigDecimal(15)) && i <= 2) {
                //类一字板，跳过
                if (isOneLineStock(data)) {
                    continue;
                }
                log.debug("{}-{}-3天内的收益大于15个点，卖出，收益={}", code, data.getDate(), rate);
                info = "3天内的收益大于10个点，卖出";
                break;
            }

            //5天内的收益大于25个点，卖出
            if (BigDecimalUtils.isGe(rate, new BigDecimal(25))) {
                //类一字板，跳过
                if (isOneLineStock(data)) {
                    continue;
                }
                log.debug("{}-{}-5天内的收益大于15个点，卖出，收益={}", code, data.getDate(), rate);
                info = "5天内的收益大于15个点，卖出";
                break;
            }
            if (i == 4) {
                log.debug("{}-{}-第5天自动卖出，卖出，收益={}", code, data.getDate(), rate);
                info = "第5天自动卖出";
            }
        }
        strategyTradeDTO.setHoldDay(holdIndex + 1);
        strategyTradeDTO.setSellDate(DateUtil.parseDate(sellDate));
        strategyTradeDTO.setSellPrice(sellPrice);
        strategyTradeDTO.setProfitRate(rate);
        strategyTradeDTO.setStrategyDescription(info);
        strategyTradeDTO.setStatus("sell");
        strategyTradeDTO.setWinFlag(BigDecimalUtils.isGt(rate, BigDecimal.ZERO) ? "y" : "n");
    }

    private boolean isOneLineStock(StockDayData data) {
        BigDecimal openPrice = data.getOpenPrice();
        BigDecimal closePrice = data.getClosePrice();
        BigDecimal range = BigDecimalUtils.subtract(closePrice, openPrice).abs();
        BigDecimal rate = BigDecimalUtils.divideToPrecent(range, openPrice);
        if (BigDecimalUtils.isLt(rate, new BigDecimal(0.5))) {
            return true;
        }
        return false;
    }
}
